

#include "Logger/LogStringFormatter.h"
#include "Logger/SafeCTimeAccess.h"

namespace logger {

/// Format string for strftime() to produce date and time in the format "YYYY-MM-DD HH:MM:SS".
static const char* STRFTIME_FORMAT_STRING = "%Y-%m-%d %H:%M:%S";

/// Size of buffer needed to hold "YYYY-MM-DD HH:MM:SS" and a null terminator.
static const int DATE_AND_TIME_STRING_SIZE = 20;

/// Separator between date/time and millis.
static const char TIME_AND_MILLIS_SEPARATOR = '.';

// Format string for sprintf() to produce millis in the format "nnn".
static const char* MILLIS_FORMAT_STRING = "%03d";

/// Size of buffer needed to hold "nnn" (milliseconds value) and a null terminator
static const int MILLIS_STRING_SIZE = 4;

/// Separator member in log lines.
static const char LEVEL_FORMATTER_SEPARATOR = ' ';

/// Number of milliseconds per second.
static const int MILLISECONDS_PER_SECOND = 1000;

LogStringFormatter::LogStringFormatter() : m_safeCTimeAccess(timing::SafeCTimeAccess::instance()) {
}

std::string LogStringFormatter::format(
    Level level,
    std::chrono::system_clock::time_point time,
    const char* text) {
    auto dateTimeString = getDateTimeString(time);
    auto millisecondString = getMillisecondString(time);

    std::stringstream stringToEmit;
    stringToEmit << (dateTimeString.empty() ? "ERROR: Date and time not logged." : dateTimeString)
                 << TIME_AND_MILLIS_SEPARATOR
                 << (millisecondString.empty() ? " ERROR: Milliseconds not logged." : millisecondString)
                 << LEVEL_FORMATTER_SEPARATOR << convertLevelToChar(level) 
                 << LEVEL_FORMATTER_SEPARATOR << text;
    return stringToEmit.str();
}

std::string LogStringFormatter::getDateTimeString(std::chrono::system_clock::time_point time) {
    bool dateTimeFailure = false;
    char dateTimeString[DATE_AND_TIME_STRING_SIZE];
    auto timeAsTime_t = std::chrono::system_clock::to_time_t(time);
    std::tm timeAsTm;

    if (!m_safeCTimeAccess->getGmtime(timeAsTime_t, &timeAsTm) ||
        0 == strftime(dateTimeString, sizeof(dateTimeString), STRFTIME_FORMAT_STRING, &timeAsTm)) {
        dateTimeFailure = true;
    }

    std::string dateTime = dateTimeFailure ? "" : dateTimeString;

    return dateTime;
}

std::string LogStringFormatter::getMillisecondString(std::chrono::system_clock::time_point time) {
    bool millisecondFailure = false;
    char millisString[MILLIS_STRING_SIZE];

    auto timeMillisPart = static_cast<int>(
        std::chrono::duration_cast<std::chrono::milliseconds>(time.time_since_epoch()).count() %
        MILLISECONDS_PER_SECOND);
    if (snprintf(millisString, sizeof(millisString), MILLIS_FORMAT_STRING, timeMillisPart) < 0) {
        millisecondFailure = true;
    }

    std::string milliseconds = millisecondFailure ? "" : millisString;

    return milliseconds;
}

}  // namespace logger
